--[[ Copyright (C) 2013 Alundaio This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License - Modified by Tronex - 2019/6/23 - NPCs transfer items they sell to trader if found - NPCs can upgrade their weapons at mechanics --]] local ini local enable_debug = false local enable_npc_trade = true local enable_npc_upgrade = true local max_upgrades_per_visit = 5 local no_trade_comm = { ["zombied"] = true, ["army"] = true, ["monolith"] = true, ["greh"] = true, ["isg"] = true, } function print_debug(...) if enable_debug then printf(...) end end function init_settings() ini = ini_file("items\\trade\\gulag_job_trade_buy_sell.ltx") enable_npc_trade = ini:r_bool_ex("settings","enable_npc_trade",true) enable_npc_upgrade = ini:r_bool_ex("settings","enable_npc_upgrade",true) max_upgrades_per_visit = ini:r_float_ex("settings","max_upgrades_per_visit") or 5 end -------------------------------- -- Callbacks -------------------------------- local function npc_on_item_take(npc,item) if enable_npc_trade then check_trade_item(npc,item) end if enable_npc_upgrade then check_tech_item(npc,item) end end function on_game_start() RegisterScriptCallback("npc_on_item_take",npc_on_item_take) init_settings() end -------------------------------- -- Trade job -------------------------------- function check_trade_item(npc,item) if not (npc:alive()) then return end local id = npc:id() local st = db.storage[id] if not (st) then return end local com = npc:character_community() if no_trade_comm[com] then return false end local sec = item:section() local bw = npc:best_weapon() or npc:active_item() if (bw and bw:id() == item:id()) then return end local slot for i=1,12 do slot = npc:item_in_slot(i) if (slot and slot:id() == item:id()) then return end end if (ini == nil or not ini:line_exist("buy_sell",sec)) then return end local buy_sell = ini:r_string_ex("buy_sell",sec) if (buy_sell == nil or buy_sell == "") then return end if not (st.trade_items) then st.trade_items = {} end st.trade_items[sec] = st.trade_items[sec] and st.trade_items[sec] + 1 or 1 buy_sell = str_explode(buy_sell,",") --print_debug("buy_sell[2]=%s",buy_sell[2]) if (st.trade_items[sec] >= tonumber(buy_sell[2])) then st.has_items_to_sell = true --print_debug("%s: wants to trade surplus items",npc:name()) end end xr_conditions.npc_has_items_to_sell = function(actor,npc,p) if not (npc) then return false end local id = type(npc.id) == "function" and npc:id() or npc.id local st = db.storage[id] if not (st) then return false end if not (st.has_items_to_sell) then return false end local smart = p and p[1] and SIMBOARD.smarts_by_names[p[1]] if (smart and smart.npc_info[id]) then local job = smart.npc_info[id].job if (job and job.idle and time_global() < job.idle) then return false end end print_debug("npc_has_items_to_sell | npc (%s) has item to sell", id) return true end xr_effects.trade_job_sell_items = function(actor,npc,p) npc_trade_buy_sell(npc) end xr_effects.trade_job_give_id = function(actor,npc,p) local board = SIMBOARD local smart = p[1] and board and board.smarts_by_names[p[1]] local id = smart and smart.npc_by_job_section["logic@"..p[2]] local npc_info = id and smart.npc_info[id] print_debug("- trade_job_give_id | smart=%s id=%s npc_info=%s job=%s",smart and smart:name(),id,npc_info ~= nil,npc_info and npc_info.job and npc_info.job.section) if not (npc_info and npc_info.job) then print_debug("- trade_job_give_id | return false") return end print_debug("- trade_job_give_id | seller_id = %s - name: %s", npc:id(), npc:name()) npc_info.job.seller_id = npc:id() end function npc_trade_buy_sell(npc) local id = npc and npc:id() local st = id and db.storage[id] if not (st) then return end if not (st.has_items_to_sell) then --print_debug("st.has_items_to_sell = nil!") return end if not (st.trade_items) then --print_debug("no items to sell") return end local smart = xr_gulag.get_npc_smart(npc) local npc_info = smart and smart.npc_info[id] if (npc_info) then npc_info.job.idle = time_global() + 20000 end print_debug("- NPC Trade | Selling") local se_obj = alife_object(id) if not (se_obj) then return end -- Get the Trader local smart = xr_gulag.get_npc_smart(npc) local npc_info = smart and smart.npc_info[id] local seller_id = npc_info and npc_info.job and npc_info.job.seller_id local seller = seller_id and (db.storage[seller_id] and db.storage[seller_id].object or level.object_by_id(seller_id)) print_debug("- NPC Trade | Trader = %s", seller and seller:name()) -- SELL ITEMS local itm,itm_base_cost,buy_sell, slot, bw,can_sell for sec,count in pairs(st.trade_items) do for i=count,1 do itm = npc:object(sec) if (itm) then can_sell = true -- Make sure item is not his best weapon bw = npc:best_weapon() if (bw and bw:id() == itm:id()) then can_sell = false end -- Make sure item is not equipped for i=1,12 do slot = npc:item_in_slot(i) if (slot and slot:id() == itm:id()) then can_sell = false break end end buy_sell = ini:r_string_ex("buy_sell",sec) if (buy_sell and buy_sell ~= "" and can_sell) then buy_sell = str_explode(buy_sell,",") -- Transfer the item if seller then npc:transfer_item(itm, seller) else alife_release_id(itm:id()) end -- Give money itm_base_cost = ini_sys:r_float_ex(sec,"cost") or 30 local money = math.floor(itm_base_cost * (tonumber(buy_sell[4]) or 1)) npc:give_money(money) -- Increase NPC rank because he is a good boy game_statistics.increment_npc_statistic(npc,"items_sold") print_debug("- NPC Trade | %s sold [%s] for %s RU", npc:name(), sec, money) -- Only sell enough till reach keep-in-stock count (param 1) if (i <= tonumber(buy_sell[1])) then break end end end end end empty_table(st.trade_items) st.has_items_to_sell = false -- BUY ITEMS local money = npc:money() if (money == nil or money <= 0) then return end if not (smart) then return end if (npc_info == nil or npc_info.job == nil) then return end if not (seller) then return end print_debug("- NPC Trade | Buying") local valid_items = utils_data.collect_section(ini,"buy_sell",true) local item_list = {} local count = 0 local function itr(npc,itm) local sec = itm:section() if (sec and valid_items[sec]) then -- count all valid items NPC has matching the buy_sell list item_list[sec] = item_list[sec] and item_list[sec] + 1 or 1 end end npc:iterate_inventory(itr,npc) local bw = npc:best_weapon() bw = bw and bw:section() local bw_ammos = bw and parse_list(ini_sys,bw,"ammo_class",true) local buy_sell_items = {} local size_t = 0 local buy_sell,total_cost for sec,v in pairs(valid_items) do buy_sell = ini:r_string_ex("buy_sell",sec) if (buy_sell and buy_sell ~= "") then buy_sell = str_explode(buy_sell,",") itm_base_cost = ini_sys:r_float_ex(sec,"cost") or 30 total_cost = math.floor(itm_base_cost * (tonumber(buy_sell[5]) or 1)) -- If section is ammo, only buy ammo for best weapon if IsItem("ammo",sec) or (bw_ammos and bw_ammos[sec]) then if (item_list[sec]) then -- only buy enough to reach restock goals (param 3) for i=item_list[sec],tonumber(buy_sell[3]) do if (money >= total_cost) then size_t = size_t + 1 buy_sell_items[size_t] = sec alife_create_item(sec, npc) npc:transfer_money(total_cost,seller) print_debug("- NPC Trade | %s bought [%s] for %s RU", npc:name(), sec, total_cost) money = money - total_cost end end else -- only buy enough to reach restock goals (param 3) for i=1,tonumber(buy_sell[3]) do if (money >= total_cost) then size_t = size_t + 1 buy_sell_items[size_t] = sec alife_create_item(sec, npc) npc:transfer_money(total_cost,seller) print_debug("- NPC Trade | %s bought [%s] for %s RU", npc:name(), sec, total_cost) money = money - total_cost end end end end end -- NPC ran out of money, end loop if (money <= 0) then break end end if (#buy_sell_items > 0) then if (dynamic_news_manager and ui_options.get("alife/dynamic_news/nearby_activity_news")) then dynamic_news_manager.get_dynamic_news():BoughtItems(npc,seller,buy_sell_items) end end end -------------------------------- -- Upgrade job -------------------------------- local function picked_unchecked(t,gr,ind) return not (t[gr] and t[gr][ind]) end local function picked_set(t,gr,ind) if (not t[gr]) then t[gr] = {} end t[gr][ind] = true end function check_tech_item(npc,item) if not (npc:alive()) then return end local id = npc:id() local st = db.storage[id] if not (st) then return end local com = npc:character_community() if no_trade_comm[com] then return false end local sec = item:section() local bw = npc:best_weapon() or npc:active_item() if (bw and bw:id() == item:id()) then return end local slot for i=1,12 do slot = npc:item_in_slot(i) if (slot and slot:id() == item:id()) then return end end if not (bw and IsWeapon(bw) and not IsMelee(bw)) then return end if (ini == nil or not ini:line_exist("buy_sell_mechanic",sec)) then return end local buy_sell = ini:r_string_ex("buy_sell_mechanic",sec) if (buy_sell == nil or buy_sell == "") then return end if not (st.tech_items) then st.tech_items = {} end st.tech_items[sec] = st.tech_items[sec] and st.tech_items[sec] + 1 or 1 buy_sell = str_explode(buy_sell,",") --print_debug("buy_sell[2]=%s",buy_sell[2]) if (st.tech_items[sec] >= tonumber(buy_sell[2])) then st.has_tech_items = true --print_debug("%s: wants to trade surplus items",npc:name()) end end xr_conditions.npc_has_tech_items = function(actor,npc,p) if not (npc) then return false end local id = type(npc.id) == "function" and npc:id() or npc.id local st = db.storage[id] if not (st) then return false end if not (st.has_tech_items) then return false end local obj = st and st.object or level.object_by_id(id) if (not obj) then print_debug("! npc_has_tech_items - no obj found for id (%s)", id) return end local bw = obj:best_weapon() or obj:active_item() if not (bw and IsWeapon(bw) and not IsMelee(bw)) then print_debug("! npc_has_tech_items - no best wpn") return end local smart = p and p[1] and SIMBOARD.smarts_by_names[p[1]] if (smart and smart.npc_info[id]) then local job = smart.npc_info[id].job if (job and job.idle and time_global() < job.idle) then return false end end print_debug("npc_has_tech_items | npc (%s) has tech item", id) return true end xr_effects.tech_job_upgrade_items = function(actor,npc,p) npc_tech_upgrade_sell(npc) end xr_effects.tech_job_give_id = function(actor,npc,p) local board = SIMBOARD local smart = p[1] and board and board.smarts_by_names[p[1]] local id = smart and smart.npc_by_job_section["logic@"..p[2]] local npc_info = id and smart.npc_info[id] print_debug("- tech_job_give_id | smart=%s id=%s npc_info=%s job=%s",smart and smart:name(),id,npc_info ~= nil,npc_info and npc_info.job and npc_info.job.section) if not (npc_info and npc_info.job) then print_debug("- tech_job_give_id | return false") return end print_debug("- tech_job_give_id | mechanic_id = %s - name: %s", npc:id(), npc:name()) npc_info.job.mechanic_id = npc:id() end function npc_tech_upgrade_sell(npc) local id = npc and npc:id() local st = id and db.storage[id] if not (st) then return end if not (st.has_tech_items) then --print_debug("st.has_tech_items = nil!") return end if not (st.tech_items) then --print_debug("no tech items to sell") return end local smart = xr_gulag.get_npc_smart(npc) local npc_info = smart and smart.npc_info[id] if (npc_info) then npc_info.job.idle = time_global() + 20000 end print_debug("- NPC Tech | Selling") local se_obj = alife_object(id) if not (se_obj) then return end -- Get the Trader local smart = xr_gulag.get_npc_smart(npc) local npc_info = smart and smart.npc_info[id] local mechanic_id = npc_info and npc_info.job and npc_info.job.mechanic_id local mechanic = mechanic_id and (db.storage[mechanic_id] and db.storage[mechanic_id].object or level.object_by_id(mechanic_id)) print_debug("- NPC Tech | Mechanic = %s", mechanic and mechanic:name()) -- SELL ITEMS local itm,itm_base_cost,buy_sell, slot, bw,can_sell for sec,count in pairs(st.tech_items) do for i=count,1 do itm = npc:object(sec) if (itm) then can_sell = true -- Make sure item is not his best weapon bw = npc:best_weapon() if (bw and bw:id() == itm:id()) then can_sell = false end -- Make sure item is not equipped for i=1,12 do slot = npc:item_in_slot(i) if (slot and slot:id() == itm:id()) then can_sell = false break end end buy_sell = ini:r_string_ex("buy_sell_mechanic",sec) if (buy_sell and buy_sell ~= "" and can_sell) then buy_sell = str_explode(buy_sell,",") -- Transfer the item if mechanic then npc:transfer_item(itm, mechanic) else alife_release_id(itm:id()) end -- Give money itm_base_cost = ini_sys:r_float_ex(sec,"cost") or 30 local money = math.floor(itm_base_cost * (tonumber(buy_sell[4]) or 1)) npc:give_money(money) -- Increase NPC rank because he is a good boy game_statistics.increment_npc_statistic(npc,"items_sold") print_debug("- NPC Tech | %s sold [%s] for %s RU", npc:name(), sec, money) -- Only sell enough till reach keep-in-stock count (param 1) if (i <= tonumber(buy_sell[1])) then break end end else print_debug("! NPC Tech | no item [%s] found for npc %s", sec, npc:name()) end end end empty_table(st.tech_items) st.has_tech_items = false -- Upgrade local money = npc:money() if (money == nil or money <= 0) then return end if not (smart) then return end if (npc_info == nil or npc_info.job == nil) then return end if not (mechanic) then return end local wpn = npc:best_weapon() if not (wpn and IsWeapon(wpn) and not IsMelee(wpn)) then return end local sec_wpn = wpn:section() print_debug("- NPC Tech | Upgrading weapons [%s]", sec_wpn) -- Upgrades calculating local picked = {} local upgrades = utils_item.get_upgrades_tree(sec_wpn) if not (upgrades and size_table(upgrades) > 0) then printf("warning: axr_trade_manager.npc_tech_upgrade_sell(): no upgrades found for [%s]", sec_wpn) return end local installed = utils_item.get_upgrades_installed(wpn, nil, true) local opposite = { [1] = 2, [2] = 1, [3] = 4, [4] = 3, [5] = 6, [6] = 5, } local money_before = money local functor = function(t,a,b) return a < b end local cnt = 0 for gr,v in spairs(upgrades,functor) do for ind,v2 in spairs(v,functor) do if installed[v2.section] then picked_set(picked, gr, ind) end end end while (cnt < max_upgrades_per_visit) and (money > 500) do local gr = random_key_table(upgrades) for ind, v in spairs(upgrades[gr],functor) do local section = v.section local cost = v.stats_cost or 2000 if upgrades[gr][ind].prop and (upgrades[gr][ind].prop[1] ~= "prop_not_available") and picked_unchecked(picked, gr, ind) and (not installed[section]) then if (not upgrades[gr][opposite[ind]]) or (upgrades[gr][opposite[ind]] and picked_unchecked(picked, gr, opposite[ind])) then picked_set(picked, gr, ind) money = money - cost print_debug("/ NPC Tech | %s installed upgrade [%s] for %s RU", npc:name(), section, cost) break end end end cnt = cnt + 1 end money = money > 100 and money or math.random(100,200) local to_install = {} local new_install = {} for gr,v in spairs(upgrades,functor) do for ind,v2 in spairs(v,functor) do if (picked[gr] and picked[gr][ind]) then to_install[#to_install + 1] = v2.section new_install[#new_install + 1] = v2.section print_debug("- NPC Tech | upgrade [%s] is new", v2.section) elseif installed[v2.section] then to_install[#to_install + 1] = v2.section print_debug("~ NPC Tech | upgrade [%s] is old", v2.section) end end end if (#to_install <= #installed) then print_debug("! NPC Tech | no new upgrades are picked") return end -- Consume NPC money npc:give_money(money - money_before) print_debug("/ NPC Tech | money taken: %s", money - money_before) -- Install upgrades inventory_upgrades.force_upgrade = true for i=1,#new_install do wpn:install_upgrade(new_install[i]) end inventory_upgrades.force_upgrade = false -- Send news if (#new_install > 0) then if (dynamic_news_manager and ui_options.get("alife/dynamic_news/nearby_activity_news")) then dynamic_news_manager.get_dynamic_news():UpgradedItems(npc, mechanic, sec_wpn, new_install) end end end